<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, onBeforeUnmount, nextTick, watch } from 'vue'
import '@xterm/xterm/css/xterm.css'
import { useWebSSH, type WebSSHServer } from '../composables/useWebSSH'

const props = defineProps<{
  targetIp?: string
  serverIp?: string
}>()

// ==================== 全局 SSE ====================
import { useSSEConnection } from '../composables/useSSEConnection'
const sseConnection = useSSEConnection()
const sshLogs = sseConnection.sshLogs
const packageStats = sseConnection.packageStats
const qpsData = sseConnection.qpsData
const sshConnectionStatus = sseConnection.sshConnectionStatus
const qpsConnectionStatus = sseConnection.qpsConnectionStatus

// ==================== 工具函数 ====================
function parseSeconds(input: unknown): number {
  if (input == null) return 0
  if (typeof input === 'number') return input
  if (typeof input === 'string') {
    const m = input.match(/[\d.]+/)
    return m ? Number(m[0]) || 0 : 0
  }
  return 0
}

function formatSeconds(sec: number): string {
  const total = Math.max(0, Math.floor(sec))
  const m = Math.floor(total / 60)
  const s = total % 60
  return m > 0 ? `${m}分${s}秒` : `${s}秒`
}

// ==================== 攻击配置和倍率计算 ====================
// 从 localStorage 读取攻击配置（自增字段值和总攻击轮次）
function getAttackConfig(): { domainIncrement: number; attackRounds: number } | null {
  try {
    const saved = localStorage.getItem('attack-config-for-amplification')
    if (!saved) return null
    const config = JSON.parse(saved)
    if (config.domainIncrement && config.attackRounds) {
      return {
        domainIncrement: Number(config.domainIncrement),
        attackRounds: Number(config.attackRounds)
      }
    }
  } catch (error) {
    console.error('读取攻击配置失败:', error)
  }
  return null
}

// 计算发包数量 = 自增字段值 × 总攻击轮次
const packetCount = computed(() => {
  const config = getAttackConfig()
  if (!config) return null
  return config.domainIncrement * config.attackRounds
})

// 计算双栈放大倍率 = 双栈总包数 / 发包数量
const dualStackAmplification = computed(() => {
  const packetCountValue = packetCount.value
  if (packetCountValue === null || packetCountValue === 0) return null
  const totalPackages = packageStats.totalPackages || 0
  if (totalPackages === 0) return 0
  return totalPackages / packetCountValue
})

// 计算单栈放大倍率 = 单栈包数 / 发包数量
const nonDualStackAmplification = computed(() => {
  const packetCountValue = packetCount.value
  if (packetCountValue === null || packetCountValue === 0) return null
  const nonDualStackPackages = packageStats.nonDualStackPackages || 0
  if (nonDualStackPackages === 0) return 0
  return nonDualStackPackages / packetCountValue
})

// 计算叠乘倍率 = 双栈放大倍率 / 单栈放大倍率
const multiplicationAmplification = computed(() => {
  const dualStack = dualStackAmplification.value
  const nonDualStack = nonDualStackAmplification.value
  if (dualStack === null || nonDualStack === null) return null
  if (nonDualStack === 0) return 0
  return dualStack / nonDualStack
})

// ==================== 闪聚期 / 波动期 ====================
const flashPeriodLabel = ref('0秒')
const wavePeriodLabel = ref('计算中…')

const flashSeconds = computed(() => parseSeconds((packageStats as any).flashBobTime))
const totalAttackSeconds = computed(() => parseSeconds((packageStats as any).attackDurationTime))

let lastFlashValue = -1
let stableCounter = 0
const STABLE_THRESHOLD = 5  // 连续5次不变 ≈ 5秒

watch([flashSeconds, totalAttackSeconds], ([flashNow, totalNow]) => {
  flashPeriodLabel.value = formatSeconds(flashNow)

  if (flashNow > 0 && flashNow === lastFlashValue) {
    stableCounter++
  } else {
    stableCounter = 0
  }
  lastFlashValue = flashNow

  if (stableCounter >= STABLE_THRESHOLD && totalNow >= flashNow && flashNow > 0) {
    wavePeriodLabel.value = formatSeconds(totalNow - flashNow)
  } else {
    wavePeriodLabel.value = stableCounter >= STABLE_THRESHOLD ? '计算中…' : '等待闪聚期稳定…'
  }
}, { immediate: true })

// ==================== 真实时间源（关键修复！）===================
// 横轴时间完全跟随 attackDurationTime，不再依赖 QPS 数据长度
const currentAttackSeconds = computed(() => parseSeconds((packageStats as any).attackDurationTime))

// 计算图表时间范围（始终从0开始，最右侧是最新时间）
const chartTimeRange = computed(() => {
  const allPoints = [
    ...(qpsData.dualStackQps as { qps: number; tSec: number }[]),
    ...(qpsData.nonDualStackQps as { qps: number; tSec: number }[])
  ]
  
  if (allPoints.length === 0) {
    // 如果没有数据点，使用当前攻击时长
    const maxTSec = Math.max(currentAttackSeconds.value, 1)
    return { minTSec: 0, maxTSec, duration: maxTSec }
  }
  
  // 获取数据点的实际时间范围（tSec 已经是相对时间，从0开始）
  const tSecValues = allPoints.map(p => p.tSec ?? 0).filter(t => t >= 0)
  if (tSecValues.length === 0) {
    const maxTSec = Math.max(currentAttackSeconds.value, 1)
    return { minTSec: 0, maxTSec, duration: maxTSec }
  }
  
  // 所有数据点的 tSec 都是从第一个数据点开始的相对时间（从0开始）
  const minTSec = 0  // 始终从0开始
  const maxTSec = Math.max(...tSecValues)  // 最右侧是最新的时间点
  
  // 计算时间范围，增加10%的留白使曲线图更饱满
  const duration = Math.max(maxTSec * 1.1, 1)  // 至少为 1，防止除零
  
  return { minTSec, maxTSec, duration }
})

// X 轴最大时间值（用于显示刻度，使用相对时间范围）
const xAxisMax = computed(() => {
  const { duration } = chartTimeRange.value
  if (duration <= 10) return 10
  // 向上取整到合适的刻度，使曲线图饱满
  if (duration <= 60) {
    return Math.ceil(duration / 10) * 10  // 10秒间隔
  } else if (duration <= 300) {
    return Math.ceil(duration / 50) * 50  // 50秒间隔
  } else {
    return Math.ceil(duration / 100) * 100  // 100秒间隔
  }
})

// X 轴时间刻度（显示从 0 开始的相对时间，与曲线路径对应）
const xAxisTimeTicks = computed(() => {
  const max = xAxisMax.value
  
  return [0, 0.25, 0.5, 0.75, 1.0].map(r => ({
    value: Math.round(max * r),  // 从 0 开始的相对时间值（秒），与曲线路径对应
    ratio: r
  }))
})

// ==================== Y轴标签 ====================
const yAxisLabels = computed(() => {
  const maxQps = qpsData.maxQps || 100
  const paddingTop = 12
  const chartHeight = 230  // 调整图表高度：250(X轴位置) - 20(顶部padding) = 230
  
  // 生成5个Y轴刻度：0, 0.25, 0.5, 0.75, 1.0
  return [0, 0.25, 0.5, 0.75, 1.0].map(ratio => {
    const value = Math.round(maxQps * ratio)
    const y = paddingTop + chartHeight * (1 - ratio)
    return { value, y }
  })
})

// ==================== QPS 曲线 ====================
const dualStackQpsPath = computed(() => {
  const points = qpsData.dualStackQps as { qps: number; tSec: number }[]
  if (!points.length) return ''

  // 确保数据点按时间排序，避免曲线路径中出现跳跃
  const sortedPoints = [...points]
    .filter(p => p.tSec != null && !isNaN(p.tSec))  // 过滤无效数据点
    .sort((a, b) => (a.tSec ?? 0) - (b.tSec ?? 0))
  
  if (sortedPoints.length === 0) return ''

  const paddingLeft = 54
  const chartWidth = 476
  const paddingTop = 12
  const chartHeight = 238  // 调整图表高度：250(X轴位置) - 12(顶部padding) = 238
  const xAxisY = 250  // X轴位置（Y=0QPS的位置）

  // 获取时间范围（始终从第0秒开始）
  const { duration } = chartTimeRange.value

  // 如果 duration 为 0 或无效，返回空路径
  if (duration <= 0 || !isFinite(duration)) return ''

  let path = ''
  
  // 第一个点固定在 (0秒, 0QPS)，即X轴和Y轴的交点
  const zeroPointX = paddingLeft  // 0秒的位置
  const zeroPointY = xAxisY  // 0QPS的位置（在X轴上）
  
  // 从(0秒, 0QPS)点开始绘制
  path += `M ${zeroPointX.toFixed(2)} ${zeroPointY.toFixed(1)}`
  
  let lastX = zeroPointX  // 记录上一个点的 x 坐标
  
  sortedPoints.forEach((point, i) => {
    const tSec = point.tSec ?? 0
    
    // tSec 已经是相对时间（从0开始），直接使用
    // 使用相对时间计算比例，确保曲线从横坐标 0 开始
    const ratio = Math.min(Math.max(tSec / duration, 0), 1)  // [0,1]
    let x = paddingLeft + ratio * chartWidth
    
    // 确保 maxQps 至少为 1，避免除零错误
    const maxQps = Math.max(qpsData.maxQps || 100, 1)
    const normalized = Math.max(0, Math.min(point.qps / maxQps, 1))  // 限制在 [0, 1] 范围内
    const y = paddingTop + chartHeight * (1 - normalized)
    
    // 确保 x 坐标严格单调递增，避免产生竖直的线
    // 如果当前点的 x 坐标小于或等于上一个点的 x 坐标，将其设置为上一个点的 x + 最小间距
    if (x <= lastX) {
      x = lastX + 0.5  // 最小间距 0.5 像素，确保严格递增
    }
    
    // 连接到当前点
    path += ` L ${x.toFixed(2)} ${y.toFixed(1)}`
    
    lastX = x
  })

  return path
})

const nonDualStackQpsPath = computed(() => {
  const points = qpsData.nonDualStackQps as { qps: number; tSec: number }[]
  if (!points.length) return ''

  // 确保数据点按时间排序，避免曲线路径中出现跳跃
  const sortedPoints = [...points]
    .filter(p => p.tSec != null && !isNaN(p.tSec))  // 过滤无效数据点
    .sort((a, b) => (a.tSec ?? 0) - (b.tSec ?? 0))
  
  if (sortedPoints.length === 0) return ''

  const paddingLeft = 54
  const chartWidth = 476
  const paddingTop = 12
  const chartHeight = 238  // 调整图表高度：250(X轴位置) - 12(顶部padding) = 238
  const xAxisY = 250  // X轴位置（Y=0QPS的位置）

  // 获取时间范围（始终从第0秒开始）
  const { duration } = chartTimeRange.value

  // 如果 duration 为 0 或无效，返回空路径
  if (duration <= 0 || !isFinite(duration)) return ''

  let path = ''
  
  // 第一个点固定在 (0秒, 0QPS)，即X轴和Y轴的交点
  const zeroPointX = paddingLeft  // 0秒的位置
  const zeroPointY = xAxisY  // 0QPS的位置（在X轴上）
  
  // 从(0秒, 0QPS)点开始绘制
  path += `M ${zeroPointX.toFixed(2)} ${zeroPointY.toFixed(1)}`
  
  let lastX = zeroPointX  // 记录上一个点的 x 坐标
  
  sortedPoints.forEach((point, i) => {
    const tSec = point.tSec ?? 0
    
    // tSec 已经是相对时间（从0开始），直接使用
    // 使用相对时间计算比例，确保曲线从横坐标 0 开始
    const ratio = Math.min(Math.max(tSec / duration, 0), 1)  // [0,1]
    let x = paddingLeft + ratio * chartWidth
    
    // 确保 maxQps 至少为 1，避免除零错误
    const maxQps = Math.max(qpsData.maxQps || 100, 1)
    const normalized = Math.max(0, Math.min(point.qps / maxQps, 1))  // 限制在 [0, 1] 范围内
    const y = paddingTop + chartHeight * (1 - normalized)
    
    // 确保 x 坐标严格单调递增，避免产生竖直的线
    // 如果当前点的 x 坐标小于或等于上一个点的 x 坐标，将其设置为上一个点的 x + 最小间距
    if (x <= lastX) {
      x = lastX + 0.5  // 最小间距 0.5 像素，确保严格递增
    }
    
    // 连接到当前点
    path += ` L ${x.toFixed(2)} ${y.toFixed(1)}`
    
    lastX = x
  })

  return path
})


// ==================== WebSSH 全局管理器 ====================
const webSSH = useWebSSH()
const websshContainer = ref<HTMLElement | null>(null)
const websshServers = ref<WebSSHServer[]>([])
const selectedWebSSHServer = ref<WebSSHServer | null>(null)

const currentWebSSHConnection = computed(() => {
  if (!selectedWebSSHServer.value) return null
  return webSSH.getConnection(selectedWebSSHServer.value.ip)
})

// 使用响应式的状态变量，因为 connection.status 不是响应式的
const websshStatus = ref<'disconnected' | 'connecting' | 'connected' | 'error'>('disconnected')

// 定期检查连接状态的定时器
const statusCheckInterval = ref<number | null>(null)

// 监听连接状态变化
watch([selectedWebSSHServer, currentWebSSHConnection], () => {
  if (currentWebSSHConnection.value) {
    websshStatus.value = currentWebSSHConnection.value.status
  } else {
    websshStatus.value = 'disconnected'
  }
}, { immediate: true })

// 定期检查连接状态（因为 connection.status 不是响应式的）
watch(selectedWebSSHServer, (server) => {
  // 清除旧的定时器
  if (statusCheckInterval.value !== null) {
    clearInterval(statusCheckInterval.value)
    statusCheckInterval.value = null
  }
  
  if (server) {
    // 每100ms检查一次连接状态
    statusCheckInterval.value = window.setInterval(() => {
      const connection = webSSH.getConnection(server.ip)
      if (connection && connection.status !== websshStatus.value) {
        websshStatus.value = connection.status
      }
    }, 100)
  } else {
    websshStatus.value = 'disconnected'
  }
}, { immediate: true })

// 组件卸载时清除定时器
onBeforeUnmount(() => {
  if (statusCheckInterval.value !== null) {
    clearInterval(statusCheckInterval.value)
    statusCheckInterval.value = null
  }
})

async function fetchWebSSHServers() {
  try {
    const response = await fetch('/get/ssh_servers_info', { headers: { Accept: 'application/json' } })
    if (!response.ok) throw new Error(`获取SSH服务器信息失败: ${response.status}`)
    const data = await response.json()
    const servers = (data || []).map((item: any) => ({
      name: (item.server_name || '未知服务器').replace(/受害/g, '受攻击').replace(/受攻击服务器/g, '靶标服务器'),
      ip: item.ip_address,
      port: 22
    }))
    
    webSSH.setServers(servers)
    websshServers.value = servers

    if (props.serverIp) {
      const target = websshServers.value.find(s => s.ip === props.serverIp)
      if (target) selectedWebSSHServer.value = target
      else if (websshServers.value.length > 0) selectedWebSSHServer.value = websshServers.value[0]
    } else if (!selectedWebSSHServer.value && websshServers.value.length > 0) {
      selectedWebSSHServer.value = websshServers.value[0]
    }
  } catch (err) {
    console.error('[AttackEffectMonitor] 获取SSH服务器信息失败:', err)
  }
}

watch(() => props.serverIp, async newIp => {
  if (newIp && websshServers.value.length > 0) {
    const target = websshServers.value.find(s => s.ip === newIp)
    if (target && selectedWebSSHServer.value?.ip !== target.ip) {
      selectedWebSSHServer.value = target
      await nextTick()
      await switchToServerTerminal(target.ip)
    }
  }
})

async function selectWebSSHServer(server: WebSSHServer) {
  selectedWebSSHServer.value = server
  await nextTick()
  await switchToServerTerminal(server.ip)
}

async function switchToServerTerminal(serverIp: string) {
  if (!websshContainer.value) return
  // 使用全局管理器重新附加容器（如果已初始化）或初始化新终端
  await webSSH.reattachContainer(serverIp, websshContainer.value)
}

async function connectWebSSH() {
  if (!selectedWebSSHServer.value) return alert('请先选择要连接的服务器')
  const connection = currentWebSSHConnection.value
  if (!connection) return
  
  // 如果已连接，断开连接
  if (connection.status === 'connected') {
    webSSH.disconnect(selectedWebSSHServer.value.ip)
    return
  }
  
  // 确保终端已初始化
  if (!websshContainer.value) return
  if (!connection.initialized) {
    await webSSH.initTerminal(selectedWebSSHServer.value.ip, websshContainer.value)
    await nextTick()
  }
  
  if (!connection.terminal) {
    return alert('终端初始化失败，请刷新页面重试')
  }
  
  // 连接WebSSH（使用全局管理器）
  webSSH.connect(selectedWebSSHServer.value.ip)
}

function clearWebSSH() {
  if (!selectedWebSSHServer.value) return
  webSSH.clear(selectedWebSSHServer.value.ip)
}

function focusWebSSHTerminal(e: MouseEvent) {
  e.stopPropagation()
  currentWebSSHConnection.value?.terminal?.focus()
}

function getServerStatus(serverIp: string) {
  return webSSH.getStatus(serverIp)
}

// ==================== 终止攻击链 ====================
const isStoppingAttack = ref(false)

async function stopAttackChain() {
  if (isStoppingAttack.value) return
  
  if (!confirm('确定要终止所有攻击链吗？此操作将停止所有正在运行的攻击服务器。')) {
    return
  }
  
  isStoppingAttack.value = true
  try {
    const response = await fetch('/api/stop_all_auth_server', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
    })
    
    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`终止攻击链失败: ${response.status} ${errorText}`)
    }
    
    const data = await response.json()
    alert('攻击链已成功终止')
    console.log('终止攻击链成功:', data)
  } catch (error) {
    console.error('终止攻击链失败:', error)
    alert('终止攻击链失败: ' + (error instanceof Error ? error.message : '未知错误'))
  } finally {
    isStoppingAttack.value = false
  }
}

// ==================== 生命周期 ====================

onMounted(async () => {
  // 注意：不再在 onMounted 时清空 QPS 数据，以保持切换页面后曲线不消失
  // 只有在真正需要重新开始攻击时才应该清空数据（例如在 TopologyDesigner 中开始新攻击时）
  // sseConnection.clearQpsData()  // 已移除：避免切换页面时清空数据
  // sseConnection.resetPackageStats()  // 已移除：避免切换页面时重置统计数据
  sseConnection.startAllConnections()
  await fetchWebSSHServers()

  // 等待容器元素准备好
  await nextTick()
  
  if (!websshContainer.value) {
    console.warn('[AttackEffectMonitor] WebSSH容器未找到')
    return
  }

  // 如果有已存在的连接，重新附加容器
  if (props.serverIp && websshServers.value.length > 0) {
    const target = websshServers.value.find(s => s.ip === props.serverIp)
    if (target) {
      selectedWebSSHServer.value = target
      await nextTick()
      await switchToServerTerminal(target.ip)
      // 如果连接已存在且处于连接状态，不需要重新连接
      // 如果未连接，则自动连接
      const connection = webSSH.getConnection(target.ip)
      if (connection.status !== 'connected' && connection.status !== 'connecting') {
        webSSH.connect(target.ip)
      }
    }
  } else if (selectedWebSSHServer.value) {
    await nextTick()
    await switchToServerTerminal(selectedWebSSHServer.value.ip)
    // 如果连接已存在且处于连接状态，不需要重新连接
    const connection = webSSH.getConnection(selectedWebSSHServer.value.ip)
    if (connection.status !== 'connected' && connection.status !== 'connecting') {
      webSSH.connect(selectedWebSSHServer.value.ip)
    }
  }
  
  // 组件卸载前，清理容器引用（但不断开WebSocket连接）
  // 这样连接可以在后台保持，下次进入页面时可以继续使用
  onBeforeUnmount(() => {
    if (selectedWebSSHServer.value) {
      webSSH.detachContainer(selectedWebSSHServer.value.ip)
    }
  })
})

onUnmounted(() => {
  // 只停止SSE连接，WebSSH连接保持
  sseConnection.stopAllConnections()
})
</script>
<template>
  <div class="monitor-page">
    <main class="monitor-main">
      <header class="page-header">
        <h2 class="page-title">打击效果评估</h2>
        <div class="page-header-right">
          <div class="pill">
            当前攻击目标
            <span class="pill-value">{{ props.targetIp?.trim() || '未设置' }}</span>
          </div>
          <div class="pill" :class="{ 'pill-success': qpsConnectionStatus === 'connected' }">
            QPS 通道
            <span class="pill-dot" />
            <span class="pill-value">
              {{ qpsConnectionStatus === 'connected' ? '已连接' : '未连接' }}
            </span>
          </div>
          <button
            class="stop-attack-btn"
            @click="stopAttackChain"
            :disabled="isStoppingAttack"
          >
            {{ isStoppingAttack ? '终止中...' : '终止攻击链' }}
          </button>
        </div>
      </header>

      <section class="top-row">
        <div class="card chart-card">
          <div class="card-header">
            <div class="card-title-left">
              <h3>攻击靶标侧DNS请求实时速率</h3>
              <span class="live-badge">
                <span class="live-dot" /> LIVE
              </span>
            </div>
          </div>
          <div class="chart-body">
            <div class="chart-container">
              <svg
                class="rate-chart"
                viewBox="0 0 560 280"
                preserveAspectRatio="xMidYMid meet"
              >
                <defs>
                  <pattern id="qps-grid" width="40" height="26" patternUnits="userSpaceOnUse">
                    <path d="M 40 0 L 0 0 0 26" fill="none" stroke="#eef2ff" stroke-width="0.5" />
                  </pattern>
                </defs>
                <rect width="560" height="280" fill="url(#qps-grid)" />
                <!-- Y 轴 -->
                <line x1="54" y1="20" x2="54" y2="250" stroke="#cbd5f5" stroke-width="1.5" />
                <!-- X 轴 -->
                <line x1="54" y1="250" x2="530" y2="250" stroke="#cbd5f5" stroke-width="1.5" />

                <!-- Y 轴刻度 -->
                <g v-for="label in yAxisLabels" :key="`y-${label.value}`">
                  <line :x1="48" :y1="label.y" :x2="54" :y2="label.y" stroke="#cbd5f5" stroke-width="1" />
                  <text :x="44" :y="label.y + 4" class="axis-label" text-anchor="end">
                    {{ label.value }}
                  </text>
                </g>
                <!-- Y 轴单位标签 -->
                <text x="44" y="10" class="unit-label" text-anchor="end">QPS</text>

                <!-- X 轴时间刻度 -->
               <g v-for="tick in xAxisTimeTicks" :key="tick.value">
                <line
                  :x1="54 + tick.ratio * 476"
                  y1="250"
                  :x2="54 + tick.ratio * 476"
                  y2="254"
                  stroke="#cbd5f5"
                  stroke-width="1"
                />
                <text
                  :x="54 + tick.ratio * 476"
                  y="268"
                  class="axis-label"
                  text-anchor="middle"
                >
                  {{ tick.value }}s
                </text>
              </g>

                <!-- QPS 曲线 -->
                <path
                  v-if="dualStackQpsPath"
                  :d="dualStackQpsPath"
                  fill="none"
                  stroke="#4f46e5"
                  stroke-width="2.5"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  v-if="nonDualStackQpsPath"
                  :d="nonDualStackQpsPath"
                  fill="none"
                  stroke="#f97316"
                  stroke-width="2.2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  stroke-dasharray="6,4"
                />

                <!-- 右下角轴说明 -->
                <text x="522" y="275" class="unit-label" text-anchor="end">时间 (s)</text>
              </svg>

              <div class="chart-legend-pill">
                <div class="legend-item">
                  <span class="legend-line legend-line-blue" />
                  <span class="legend-text">双栈请求</span>
                </div>
                <div class="legend-item">
                  <span class="legend-line legend-line-orange" />
                  <span class="legend-text">单栈请求</span>
                </div>
              </div>
            </div>

            <div class="period-row">
              <div class="period-chip">
                <span class="period-label">闪聚期</span>
                <span class="period-desc">0～{{ flashPeriodLabel }}</span>
              </div>
              <div class="period-chip">
                <span class="period-label">波动期</span>
                <span class="period-desc">{{ flashPeriodLabel }}~{{ wavePeriodLabel }}</span>
              </div>
            </div>
          </div>
        </div>

        <div class="right-column">
          <div class="card stats-card">
            <div class="card-header compact">
              <h3>攻击实时状态</h3>
            </div>
            <div class="stats-body">
              <!-- 第一排 -->
              <div class="stat-box">
                <div class="stat-label">双栈总包数</div>
                <div class="stat-value">{{ packageStats.totalPackages || 0 }}</div>
              </div>
              <div class="stat-box">
                <div class="stat-label">单栈包数</div>
                <div class="stat-value warning">
                  {{ packageStats.nonDualStackPackages || 0 }}
                </div>
              </div>
              <div class="stat-box">
                <div class="stat-label">持续时间</div>
                <div class="stat-value primary">{{ packageStats.attackDuration || '0秒' }}</div>
              </div>
              <!-- 第二排 -->
              <div class="stat-box">
                <div class="stat-label">双栈放大倍率</div>
                <div class="stat-value">
                  {{
                    dualStackAmplification === null
                      ? '-'
                      : dualStackAmplification.toFixed(2)
                  }}
                </div>
              </div>
              <div class="stat-box">
                <div class="stat-label">单栈放大倍率</div>
                <div class="stat-value warning">
                  {{
                    nonDualStackAmplification === null
                      ? '-'
                      : nonDualStackAmplification.toFixed(2)
                  }}
                </div>
              </div>
              <div class="stat-box">
                <div class="stat-label">叠乘倍率</div>
                <div class="stat-value">
                  {{
                    multiplicationAmplification === null
                      ? '-'
                      : multiplicationAmplification.toFixed(2)
                  }}
                </div>
              </div>
            </div>
          </div>

          <div class="card log-card">
            <div class="card-header">
              <h3>攻击靶标侧实时日志</h3>
              <div class="log-header-right">
                <div class="connection-status" :class="sshConnectionStatus">
                  <span class="status-indicator" />
                  <span class="status-text">
                    {{
                      sshConnectionStatus === 'connected'
                        ? '已连接'
                        : sshConnectionStatus === 'connecting'
                          ? '连接中...'
                          : sshConnectionStatus === 'error'
                            ? '连接错误'
                            : '未连接'
                    }}
                  </span>
                </div>
                <button
                  class="mini-btn"
                  @click="sseConnection.startAllConnections"
                  :disabled="sshConnectionStatus === 'connecting'"
                >
                  {{ sshConnectionStatus === 'connected' ? '重连' : '连接' }}
                </button>
                <button class="mini-btn ghost" @click="sseConnection.clearSSHLogs">清空</button>
              </div>
            </div>
            <div class="log-body">
              <div class="ssh-logs">
                <div
                  v-for="(log, i) in sshLogs"
                  :key="i"
                  class="ssh-log-line"
                  :class="{
                    error: log.includes('错误'),
                    warning: log.includes('警告'),
                    success: log.includes('成功')
                  }"
                >
                  {{ log }}
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>

      <section class="card webssh-card">
        <div class="card-header webssh-header">
          <div class="webssh-title">
            <span class="webssh-dot" />
            <span>WebSSH 终端控制</span>
          </div>
          <div class="webssh-controls">
            <div class="webssh-server-buttons">
              <button
                v-for="server in websshServers"
                :key="server.ip"
                class="server-btn"
                :class="{
                  active: selectedWebSSHServer?.ip === server.ip,
                  connected: getServerStatus(server.ip) === 'connected',
                  connecting: getServerStatus(server.ip) === 'connecting'
                }"
                @click="selectWebSSHServer(server)"
                :title="`${server.name} (${server.ip}:${server.port})`"
              >
                {{ server.name }}
              </button>
            </div>
            <div class="connection-status small" :class="websshStatus">
              <span class="status-indicator" />
              <span class="status-text">
                {{
                  websshStatus === 'connected'
                    ? '已连接'
                    : websshStatus === 'connecting'
                      ? '连接中...'
                      : websshStatus === 'error'
                        ? '连接错误'
                        : '未连接'
                }}
              </span>
            </div>
            <button
              class="mini-btn"
              @click="connectWebSSH"
              :disabled="websshStatus === 'connecting' || !selectedWebSSHServer"
            >
              {{ websshStatus === 'connected' ? '断开' : '连接终端' }}
            </button>
            <button class="mini-btn ghost" @click="clearWebSSH" :disabled="websshStatus !== 'connected'">
              清空
            </button>
          </div>
        </div>
        <div class="webssh-body" @click="focusWebSSHTerminal">
          <div ref="websshContainer" class="webssh-terminal"></div>
          <div v-if="websshStatus !== 'connected'" class="webssh-placeholder">
            <div class="plug-icon">🔌</div>
            <div class="placeholder-text">终端已断开，请选择服务器并点击"连接终端"</div>
          </div>
        </div>
      </section>
    </main>
  </div>
</template>

<style scoped>
/* 整页允许滚动，但默认至少占满一屏 */
.monitor-page {
  /* 视口高度 - AppLayout 顶部条(56px) - page-content 上下 padding(18px+18px) */
  min-height: calc(100vh - 56px - 36px);
  height: calc(100vh - 56px - 36px);
  overflow: hidden;
  background: linear-gradient(135deg, #f4f6fb 0%, #e5ebff 100%);
  display: flex;
}

/* 三行布局：header + 上半部分 + 下半部分(WebSSH) */
.monitor-main {
  flex: 1;
  padding: 6px 10px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 6px; /* header/top-row/webssh间距一致 */
  height: 100%;
  min-height: 0;
  overflow: hidden;
}

/* 顶部标题行 */
.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 2px;
}

.page-title {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
  color: #0f172a;
}

.page-header-right {
  display: flex;
  gap: 6px;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.9);
  font-size: 11px;
  color: #64748b;
  box-shadow: 0 2px 6px rgba(15, 23, 42, 0.06);
}

.pill-success {
  background: #ecfdf3;
  color: #166534;
}

.pill-value {
  font-weight: 600;
  color: #0f172a;
}

.pill-success .pill-value {
  color: #15803d;
}

.pill-dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: #22c55e;
}

.stop-attack-btn {
  padding: 4px 12px;
  border-radius: 6px;
  border: 1px solid #ef4444;
  background: #ef4444;
  color: #fff;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.15s ease;
  white-space: nowrap;
}

.stop-attack-btn:hover:not(:disabled) {
  background: #dc2626;
  border-color: #dc2626;
  box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
}

.stop-attack-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* 卡片通用样式 */
.card {
  background: #ffffff;
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
  border: 1px solid rgba(148, 163, 184, 0.12);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-sizing: border-box; /* 确保边框包含在高度内 */
}

.card-header {
  padding: 4px 8px;
  border-bottom: 1px solid rgba(226, 232, 240, 0.9);
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: #ffffff;
  flex: none;
}

.card-header.compact {
  padding-top: 3px;
  padding-bottom: 3px;
}

.card-header h3 {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  color: #0f172a;
}

/* 上半部分：曲线 + 右侧统计/日志 */
.top-row {
  display: flex;
  gap: 6px;
  min-height: 0;
  align-items: stretch; /* 确保左右两侧高度一致 */
  margin-bottom: 0; /* 确保没有额外的margin影响间距 */
  flex: 1 1 auto;
  min-height: 0;
}

/* 左侧曲线卡片 */
.chart-card {
  flex: 1.5;
  min-width: 0;
  flex-basis: 10px;
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.card-title-left {
  display: flex;
  align-items: center;
  gap: 6px;
}

.live-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 5px;
  border-radius: 999px;
  background: #ecfdf3;
  color: #15803d;
  font-size: 10px;
  font-weight: 500;
}

.live-dot {
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: #22c55e;
}

.chart-body {
  padding: 2px 6px 4px;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.chart-container {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  justify-content: center;   /* 水平居中 SVG */
  align-items: center;       /* 垂直居中 SVG */
  padding-top: 10px;         /* 可选：让文字标签不贴边 */
  padding-bottom: 10px;
}

.rate-chart {
  width: 100%;
  height: 100%;
  display: block;
}

.axis-label {
  font-size: 10px;
  fill: #9ca3af;
  font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
}

.unit-label {
  font-size: 10px;
  fill: #9ca3af;
  font-weight: 600;
}

/* 图例胶囊 */
.chart-legend-pill {
  position: absolute;
  top: 5px;
  right: 5px;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid rgba(226, 232, 240, 0.9);
  box-shadow: 0 4px 10px rgba(15, 23, 42, 0.08);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  backdrop-filter: blur(4px);
}

.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  color: #1f2933;
}

.legend-line {
  width: 14px;
  height: 3px;
  border-radius: 999px;
}

.legend-line-blue {
  background: #4f46e5;
}

.legend-line-orange {
  background: repeating-linear-gradient(
    to right,
    #f97316,
    #f97316 6px,
    transparent 6px,
    transparent 10px
  );
}

/* 闪聚期 / 波动期 */
.period-row {
  flex: none;
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 2px;
  padding-bottom: 2px;
}

.period-chip {
  min-width: 118px;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid rgba(199, 210, 254, 0.9);
  background: #f9fafb;
  font-size: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-shadow: 0 2px 6px rgba(129, 140, 248, 0.1);
}

.period-label {
  font-weight: 700;
  font-size: 12px;
  color: #4f46e5;
}

.period-desc {
  color: #6b7280;
  font-size: 12px;
}

/* 右侧列：上统计 + 下日志 */
.right-column {
  flex: 1.8;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  height: 100%;
  min-height: 0;
  align-self: stretch;
  box-sizing: border-box; /* 确保边框包含在高度内 */
  overflow: hidden; /* 防止内容溢出 */
}

/* 累计统计卡片：高度较小且固定 */
.stats-card {
  flex: 0 0 38%;
  display: flex;
  flex-direction: column;
  overflow: hidden; /* 防止内容溢出 */
  min-height: 0;
}

.stats-body {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 1fr);
  gap: 4px;
  padding: 4px 6px;
}

.stat-box {
  border-radius: 8px;
  padding: 3px 4px;
  background: #f8fafc;
  border: 1px solid rgba(226, 232, 240, 0.8);
  text-align: center;
}

.stat-label {
  font-size: 10px;
  color: #94a3b8;
  margin-bottom: 1px;
}

.stat-value {
  font-size: 13px;
  font-weight: 600;
  color: #0f172a;
}

.stat-value.primary {
  color: #4f46e5;
}

.stat-value.warning {
  color: #ea580c;
}

/* 日志卡片 */
.log-card {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden; /* 防止内容溢出 */
}

/* 日志头部不变 */
.log-header-right {
  display: flex;
  align-items: center;
  gap: 6px;
}

.connection-status {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 6px;
  border-radius: 999px;
  border: 1px solid #e5e7eb;
  font-size: 10px;
  color: #6b7280;
  background: #f9fafb;
}

.connection-status.small {
  padding: 1px 6px;
  font-size: 10px;
}

.connection-status.connected {
  background: #ecfdf3;
  border-color: #22c55e;
  color: #15803d;
}

.connection-status.connecting {
  background: #fffbeb;
  border-color: #f59e0b;
  color: #92400e;
}

.connection-status.error {
  background: #fef2f2;
  border-color: #ef4444;
  color: #b91c1c;
}

.status-indicator {
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: currentColor;
}

.mini-btn {
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid #4f46e5;
  background: #4f46e5;
  color: #fff;
  font-size: 10px;
  cursor: pointer;
  transition: all 0.15s ease;
}

.mini-btn.ghost {
  background: #fff;
  color: #4f46e5;
}

.mini-btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* 日志内容：内部滚动，卡片高度固定 */
.log-body {
  flex: 1;
  min-height: 0;
  padding: 3px 6px;
  display: flex;
  flex-direction: column;
  /* 关键：外层不再增长，由内部滚动 */
  overflow: hidden;
}

.ssh-logs {
  flex: 1;
  /* 关键：内容溢出时在内部滚动 */
  overflow-y: auto;
  font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
  font-size: 11px;
  line-height: 1.4;
  color: #0f172a;
}
.ssh-log-line {
  white-space: pre-wrap;
  word-break: break-all;
  margin-bottom: 2px;
}

.ssh-log-line.success {
  color: #15803d;
}

.ssh-log-line.warning {
  color: #b45309;
}

.ssh-log-line.error {
  color: #b91c1c;
}

/* WebSSH 区域 */
.webssh-card {
  min-height: 0;
  display: flex;
  flex-direction: column;
  flex: 0 0 320px;
  height: 320px;
  max-height: 320px;
  margin-top: 0;
}

.webssh-header {
  padding-top: 4px;
  padding-bottom: 4px;
}

.webssh-title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 500;
}

.webssh-dot {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: linear-gradient(135deg, #4f46e5, #6366f1);
}

.webssh-controls {
  display: flex;
  align-items: center;
  gap: 6px;
}

.webssh-server-buttons {
  display: flex;
  gap: 4px;
}

.server-btn {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10px;
  border: 1px solid #e5e7eb;
  background: #fff;
  color: #374151;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.15s ease;
}

.server-btn:hover {
  background: #f9fafb;
}

.server-btn.active {
  background: linear-gradient(135deg, #4f46e5, #6366f1);
  color: #fff;
  border-color: transparent;
}

.server-btn.connected:not(.active) {
  border-color: #22c55e;
}

.server-btn.connecting:not(.active) {
  border-color: #f97316;
}

.webssh-body {
  position: relative;
  flex: 1;
  min-height: 0;
  padding: 0;
  background: #f9fafb;
  border-top: 1px solid rgba(226, 232, 240, 0.9);
}

.webssh-terminal {
  width: 100%;
  height: 100%;
  border-radius: 0 0 10px 10px;
  overflow: hidden;
  background: #ffffff;
}

/* xterm 内部留白 */
.webssh-terminal :deep(.xterm) {
  padding: 4px 0 0 8px;
}

.webssh-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  color: #94a3b8;
  font-size: 12px;
}

.plug-icon {
  font-size: 24px;
  margin-bottom: 6px;
}
</style>
